/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.app;

import android.content.Context;
import android.content.res.Resources;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
import android.view.ContextThemeWrapper;
import android.view.Display;
import android.view.Gravity;
import android.view.WindowManagerImpl;
import android.os.Handler;
import android.os.Message;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;

/**
 *  presentations的积基类
 *  演示文稿（presentation）是一种特殊类型的对话框，其目的是在辅助显示器上呈现内容。
 *  {@link Presentation}在创建时与目标{@link Display}相关联，并根据显示的度量配置其上下文和资源配置。
 *  
 *  值得注意的是，演示文稿的{@link Context}与其包含的{@link Activity}的上下文不同。
 *  使用演示文稿自己的上下文来扩充演示文稿的布局并加载其他资源以确保加载目标显示的正确大小和密度的资源是非常重要的。
 *  
 *  演示文稿被自动取消（请参阅{@link Dialog＃cancel（）}），当它所连接的显示器被删除时。 
 *  每当 activity本身暂停或恢复时， activity应负责暂停和恢复演示文稿中正在播放的任何内容。
 *
 * 选择演示文稿显示：
 * 在展示{@link Presentation}之前，选择它将出现的{@link Display}很重要。 
 * 选择演示文稿显示有时很困难，因为可能会连接多个显示。
 * 应用程序应该让系统选择合适的演示文稿显示，而不是试图猜测哪种显示效果最佳。
 * 
 * 有两种主要的方法来选择一个{@link Display}：
 * 
 * <h4>使用媒体路由器选择演示文稿显示</h4>
 *
 * 选择演示文稿显示的最简单方法是使用@link android.media.MediaRouter MediaRouter}  API。
 * 媒体路由器服务会跟踪系统上哪些音频和视频路由可用。
 * 媒体路由器在选择或未选择路由或路由的首选演示显示更改时发送通知。 
 * 因此，应用程序可以简单地观看这些通知，并自动在首选演示文稿显示中显示或取消演示。
 *
 * 首选的演示文稿显示是媒体路由器建议应用程序在要显示辅助显示内容时应使用的显示。
 * 有时可能没有首选的演示文稿显示，在这种情况下，应用程序应该在本地显示其内容而不使用演示文稿。
 * 
 * 以下介绍如何使用媒体路由器利用{@link android.media.MediaRouter.RouteInfo#getPresentationDisplay()}.
 * 首选演示文稿来显示：
 * 
 * <pre>
 * MediaRouter mediaRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
 * MediaRouter.RouteInfo route = mediaRouter.getSelectedRoute();
 * if (route != null) {
 *     Display presentationDisplay = route.getPresentationDisplay();
 *     if (presentationDisplay != null) {
 *         Presentation presentation = new MyPresentation(context, presentationDisplay);
 *         presentation.show();
 *     }
 * }</pre>
 * 
 * Api Demos的以下示例代码演示了如何使用媒体路由器自动切换显示主要活动中的内容，并在演示文稿显示可用时显示演示文稿中的内容。
 * 
 * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/PresentationWithMediaRouterActivity.java
 *      activity}
 *
 * <h4>使用显示管理器选择演示文稿显示</h4>
 * 另一种选择演示文稿显示的方法是直接使用{@link DisplayManager} API。
 * 显示管理器服务提供枚举和描述连接到系统的所有显示的功能，包括可用于演示的显示。
 *
 * 显示管理器跟踪系统中的所有显示。 但是，并非所有显示器都适合用于显示演示文稿。
 * 例如，如果一个活动试图在主显示器上显示演示文稿，它可能会掩盖其自己的内容（就像在活动之上打开对话框一样）
 *
 * 以下是如何使用{@link DisplayManager#getDisplays(String)} and
 * {@link DisplayManager#DISPLAY_CATEGORY_PRESENTATION}类别来识别合适的显示器以显示演示文稿。
 *
 * 
 * <pre>
 * DisplayManager displayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
 * Display[] presentationDisplays = displayManager.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
 * if (presentationDisplays.length > 0) {
 *     // If there is more than one suitable presentation display, then we could consider
 *     // giving the user a choice.  For this example, we simply choose the first display
 *     // which is the one the system recommends as the preferred presentation display.
 *     Display display = presentationDisplays[0];
 *     Presentation presentation = new MyPresentation(context, presentationDisplay);
 *     presentation.show();
 * }</pre>
 * 
 * Api Demos中的以下示例代码演示了如何使用显示管理器枚举显示并同时在多个演示文稿显示中显示内容
 * 
 * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/PresentationActivity.java
 *      activity}
 *
 * @see android.media.MediaRouter#ROUTE_TYPE_LIVE_VIDEO 了解有关实况视频路线的信息以及如何获取当前媒体路线的首选演示显示。
 * @see DisplayManager 有关如何在添加或删除显示时枚举显示和接收通知的信息
 */
public class Presentation extends Dialog {
    private static final String TAG = "Presentation";

    private static final int MSG_CANCEL = 1;

    private final Display mDisplay;
    private final DisplayManager mDisplayManager;

    /**
     * Creates a new presentation that is attached to the specified display
     * using the default theme.
     *
     * @param outerContext The context of the application that is showing the presentation.
     * The presentation will create its own context (see {@link #getContext()}) based
     * on this context and information about the associated display.
     * @param display The display to which the presentation should be attached.
     */
    public Presentation(Context outerContext, Display display) {
        this(outerContext, display, 0);
    }

    /**
     * Creates a new presentation that is attached to the specified display
     * using the optionally specified theme.
     *
     * @param outerContext The context of the application that is showing the presentation.
     * The presentation will create its own context (see {@link #getContext()}) based
     * on this context and information about the associated display.
     * @param display The display to which the presentation should be attached.
     * @param theme A style resource describing the theme to use for the window.
     * See <a href="{@docRoot}guide/topics/resources/available-resources.html#stylesandthemes">
     * Style and Theme Resources</a> for more information about defining and using
     * styles.  This theme is applied on top of the current theme in
     * <var>outerContext</var>.  If 0, the default presentation theme will be used.
     */
    public Presentation(Context outerContext, Display display, int theme) {
        super(createPresentationContext(outerContext, display, theme), theme, false);

        mDisplay = display;
        mDisplayManager = (DisplayManager)getContext().getSystemService(Context.DISPLAY_SERVICE);

        getWindow().setGravity(Gravity.FILL);
        setCanceledOnTouchOutside(false);
    }

    /**
     * Gets the {@link Display} that this presentation appears on.
     *
     * @return The display.
     */
    public Display getDisplay() {
        return mDisplay;
    }

    /**
     * Gets the {@link Resources} that should be used to inflate the layout of this presentation.
     * This resources object has been configured according to the metrics of the
     * display that the presentation appears on.
     *
     * @return The presentation resources object.
     */
    public Resources getResources() {
        return getContext().getResources();
    }

    @Override
    protected void onStart() {
        super.onStart();
        mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);

        // Since we were not watching for display changes until just now, there is a
        // chance that the display metrics have changed.  If so, we will need to
        // dismiss the presentation immediately.  This case is expected
        // to be rare but surprising, so we'll write a log message about it.
        if (!isConfigurationStillValid()) {
            Log.i(TAG, "Presentation is being immediately dismissed because the "
                    + "display metrics have changed since it was created.");
            mHandler.sendEmptyMessage(MSG_CANCEL);
        }
    }

    @Override
    protected void onStop() {
        mDisplayManager.unregisterDisplayListener(mDisplayListener);
        super.onStop();
    }

    /**
     * Inherited from {@link Dialog#show}. Will throw
     * {@link android.view.WindowManager.InvalidDisplayException} if the specified secondary
     * {@link Display} can't be found.
     */
    @Override
    public void show() {
        super.show();
    }

    /**
     * Called by the system when the {@link Display} to which the presentation
     * is attached has been removed.
     *
     * The system automatically calls {@link #cancel} to dismiss the presentation
     * after sending this event.
     *
     * @see #getDisplay
     */
    public void onDisplayRemoved() {
    }

    /**
     * Called by the system when the properties of the {@link Display} to which
     * the presentation is attached have changed.
     *
     * If the display metrics have changed (for example, if the display has been
     * resized or rotated), then the system automatically calls
     * {@link #cancel} to dismiss the presentation.
     *
     * @see #getDisplay
     */
    public void onDisplayChanged() {
    }

    private void handleDisplayRemoved() {
        onDisplayRemoved();
        cancel();
    }

    private void handleDisplayChanged() {
        onDisplayChanged();

        // We currently do not support configuration changes for presentations
        // (although we could add that feature with a bit more work).
        // If the display metrics have changed in any way then the current configuration
        // is invalid and the application must recreate the presentation to get
        // a new context.
        if (!isConfigurationStillValid()) {
            cancel();
        }
    }

    private boolean isConfigurationStillValid() {
        DisplayMetrics dm = new DisplayMetrics();
        mDisplay.getMetrics(dm);
        return dm.equalsPhysical(getResources().getDisplayMetrics());
    }

    private static Context createPresentationContext(
            Context outerContext, Display display, int theme) {
        if (outerContext == null) {
            throw new IllegalArgumentException("outerContext must not be null");
        }
        if (display == null) {
            throw new IllegalArgumentException("display must not be null");
        }

        Context displayContext = outerContext.createDisplayContext(display);
        if (theme == 0) {
            TypedValue outValue = new TypedValue();
            displayContext.getTheme().resolveAttribute(
                    com.android.internal.R.attr.presentationTheme, outValue, true);
            theme = outValue.resourceId;
        }

        // Derive the display's window manager from the outer window manager.
        // We do this because the outer window manager have some extra information
        // such as the parent window, which is important if the presentation uses
        // an application window type.
        final WindowManagerImpl outerWindowManager =
                (WindowManagerImpl)outerContext.getSystemService(Context.WINDOW_SERVICE);
        final WindowManagerImpl displayWindowManager =
                outerWindowManager.createPresentationWindowManager(display);
        return new ContextThemeWrapper(displayContext, theme) {
            @Override
            public Object getSystemService(String name) {
                if (Context.WINDOW_SERVICE.equals(name)) {
                    return displayWindowManager;
                }
                return super.getSystemService(name);
            }
        };
    }

    private final DisplayListener mDisplayListener = new DisplayListener() {
        @Override
        public void onDisplayAdded(int displayId) {
        }

        @Override
        public void onDisplayRemoved(int displayId) {
            if (displayId == mDisplay.getDisplayId()) {
                handleDisplayRemoved();
            }
        }

        @Override
        public void onDisplayChanged(int displayId) {
            if (displayId == mDisplay.getDisplayId()) {
                handleDisplayChanged();
            }
        }
    };

    private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_CANCEL:
                    cancel();
                    break;
            }
        }
    };
}
